package magnet.processor.registry;

import com.squareup.javapoet.ClassName;
import magnet.internal.Index;
import magnet.internal.InstanceFactory;
import magnet.processor.common.AnnotationValueExtractor;

import javax.lang.model.element.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

class RegistryParser {

    private AnnotationValueExtractor extractor;

    public RegistryParser(AnnotationValueExtractor extractor) {
        this.extractor = extractor;
    }

    public Model.Registry parse(PackageElement element) {

        ArrayList<Model.InstanceFactory> instanceFactories = new ArrayList<Model.InstanceFactory>();
        List<? extends Element> factoryIndexElements = element.getEnclosedElements() == null ? new ArrayList<>() : element.getEnclosedElements();

        for (Element factoryIndexElement : factoryIndexElements) {

            annotationValues(factoryIndexElement, new AnnotationValuesBlock() {
                @Override
                public void block(ClassName factoryType, ClassName factoryClass, ClassName instanceType, String classifier) {
                    if (isOfType(factoryType, InstanceFactory.class)) {
                        instanceFactories.add(
                                new Model.InstanceFactory(factoryClass,instanceType,classifier)
                        );
                    }
                }
            });
        }

        return new Model.Registry(instanceFactories);
    }

    private interface AnnotationValuesBlock {
        void block(ClassName factoryType,ClassName factoryClass,ClassName instanceType,String classifier);
    }

    private void annotationValues(Element element, AnnotationValuesBlock block) {

        TypeElement factoryType = null;
        TypeElement factoryClass = null;
        String instanceType = null;
        String classifier = null;

        List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            if (Index.class.getName().equals(annotationMirror.getAnnotationType().toString())) {
                Set<? extends Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>> entries = annotationMirror.getElementValues().entrySet();
                for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : entries) {
                    String entryName = entry.getKey().getSimpleName().toString();
                    AnnotationValue entryValue = entry.getValue();
                    switch (entryName) {
                        case "factoryType":
                            factoryType = extractor.getTypeElement(entryValue);
                            break;
                        case "factoryClass":
                            factoryClass = extractor.getTypeElement(entryValue);
                            break;
                        case "instanceType":
                            instanceType = extractor.getStringValue(entryValue);
                            break;
                        case "classifier":
                            classifier = extractor.getStringValue(entryValue);
                            break;
                    }
                }
                break;
            }
        }
        
        if (block != null) {
            block.block(
                    ClassName.get(requireNotNull(factoryType)),
                    ClassName.get(requireNotNull(factoryClass)),
                    ClassName.bestGuess(requireNotNull(instanceType)),
                    requireNotNull(classifier)
            );
        }
    }

    private <T> T requireNotNull(T value) {
        if (value == null) {
            throw new IllegalArgumentException("Required value was null.");
        }
        return value;
    }

    private boolean isOfType(ClassName className,Class type) {
        return className.packageName().equals(type.getPackage().getName()) && className.simpleName().equals(type.getSimpleName());
    }
}
